package com.bjpowerndoe.ylb.web.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.bjpowerndoe.ylb.common.AppConsts;
import com.bjpowerndoe.ylb.common.AppTools;
import com.bjpowerndoe.ylb.common.RedisKey;
import com.bjpowerndoe.ylb.domain.BidInfoPO;
import com.bjpowerndoe.ylb.domain.FinanceAccountPO;
import com.bjpowerndoe.ylb.domain.ProductInfoPO;
import com.bjpowerndoe.ylb.domain.RechargeRecordPO;

import com.bjpowerndoe.ylb.map.BidProductNameMap;
import com.bjpowerndoe.ylb.mapper.BidInfoMapper;
import com.bjpowerndoe.ylb.mapper.FinanceAccountMapper;
import com.bjpowerndoe.ylb.mapper.ProductInfoMapper;
import com.bjpowerndoe.ylb.web.enums.CodeEnum;
import com.bjpowerndoe.ylb.web.model.CommonResult;
import com.bjpowerndoe.ylb.web.model.dto.InvestRankDTO;
import com.bjpowerndoe.ylb.web.model.dto.UserDTO;
import com.bjpowerndoe.ylb.web.service.inter.InvestService;
import com.bjpowerndoe.ylb.web.service.inter.UserService;
import com.sun.org.apache.bcel.internal.classfile.Code;
import lombok.RequiredArgsConstructor;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.ZSetOperations;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Set;

/**
 *
 */
@RequiredArgsConstructor
@Service
public class InvestServiceImpl implements InvestService {


    //模板类
    private final StringRedisTemplate stringRedisTemplate;

    private final FinanceAccountMapper accountMapper;
    private final BidInfoMapper bidInfoMapper;
    private final ProductInfoMapper productInfoMapper;

    private final UserService userService;

    @Override
    public List<InvestRankDTO> getInvestRank() {

        //TypedTuple:表示zset中每一个成员包含value和score
        Set<ZSetOperations.TypedTuple<String>> typedTuples = stringRedisTemplate.opsForZSet()
                .reverseRangeWithScores(RedisKey.INVEST_RANK_TOP, 0, 2);

        List<InvestRankDTO> dtoList = new ArrayList<>();
        typedTuples.forEach( tuple -> {
            dtoList.add(new InvestRankDTO(tuple.getValue(),tuple.getScore()));
        });
        return dtoList;
    }

    /**
     * 投资操作
     * 1.判断账号余额是否充足
     *  使用mysql的 for update 排它锁
     *  select * from 表 for update
     *
     *  行级  where 主键或唯一索引
     *  表级  反之就是表级别锁
     * 2.判断购买金额和产品剩余金额
     * 3.扣除账号余额
     * 4.扣除产品剩余可投资金额
     * 5.生成投资金额(b_bid_info)
     * 6.判断是否满标（卖完了）
     * 7.更新产品为满标状态，满标时间
     */
    @Transactional(rollbackFor = Exception.class)
    @Override
    public CodeEnum buyProduct(Integer uid, Integer productId, BigDecimal investMoney) {
        //1.查询账号余额
        FinanceAccountPO account = accountMapper.selectOne(new QueryWrapper<FinanceAccountPO>()
                .eq("uid", uid).last(" for update "));

        if(account == null){
            return CodeEnum.ACCOUNT_NOT_EXISTS_ERR;
        }

        //2.账号余额不足
        if( investMoney.compareTo(account.getAvailableMoney()) == 1 ){
            return CodeEnum.ACCOUNT_MONEY_NOT_ENOUGHT_ERR;
        }

        //3.查询理财产品
        ProductInfoPO productInfo = productInfoMapper.selectOne(new QueryWrapper<ProductInfoPO>()
                .eq("id", productId).last(" for update "));

        if( productInfo == null){
            return CodeEnum.PRODUCT_NOT_EXISTS_ERR;
        }

        //4.产品剩余金额
        if( investMoney.compareTo(productInfo.getLeftProductMoney()) == 1 ){
            return CodeEnum.PRODUCT_LEFTMONEY_NOT_ENOUGH_ERR;
        }

        //5.扣除账号余额
        int rows = accountMapper.updateAvaiableMoneyForInvest(uid, investMoney);
        if(rows < 1){
            throw new RuntimeException("投资，扣除账号余额失败");
        }

        //6.扣除产品剩余可投资金额
        rows  = productInfoMapper.updateLeftProductMoneyForInvest(productId,investMoney);
        if( rows < 1){
            throw new RuntimeException("投资，扣除产品剩余可投资金额失败");
        }

        //7.新增投资记录
        BidInfoPO bidInfo = new BidInfoPO();
        bidInfo.setBidMoney(investMoney);
        bidInfo.setBidStatus(AppConsts.STATUS_BID_SUCC);
        bidInfo.setBidTime(new Date());
        bidInfo.setProdId(productId);
        bidInfo.setUid(uid);
        bidInfoMapper.insert(bidInfo);

        //8.判断是否满标
        ProductInfoPO queryProductInfo = productInfoMapper.selectById(productId);
        if( queryProductInfo.getLeftProductMoney().compareTo(new BigDecimal("0")) == 0){
            //满标，更新状态和时间
            queryProductInfo.setProductStatus(AppConsts.STATUS_PRODUCT_MAN_BIAO);
            queryProductInfo.setProductFullTime(new Date());
            rows = productInfoMapper.updateById(queryProductInfo);
            if( rows < 1){
                throw new RuntimeException("投资，更新产品的满标状态失败");
            }
        }

        return CodeEnum.OK;
    }


    @Override
    public List<BidProductNameMap> queryBidListByUid(Integer uid, Integer pageNo, Integer pageSize) {

        int offset = (pageNo - 1) *pageSize;
        return bidInfoMapper.selectBidListByUid(uid,offset,pageSize);
    }

    @Override
    public void modifyInvestRank(Integer uid, BigDecimal investMoney) {
        //ZSet类型
        String key = RedisKey.INVEST_RANK_TOP;

        //查询手机号
        UserDTO userDTO = userService.queryUserById(uid);
        if( userDTO != null){
            stringRedisTemplate.opsForZSet().
                    incrementScore(key,userDTO.getPhone(),investMoney.doubleValue() );
        }
    }
}
